﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace SimplexEngine
{
    public class MD2ResourceLoader : IResourceLoader
    {
        private struct MD2Header
        {
            public Int32 magic_number;
            public Int32 version;
            public Int32 skin_width;
            public Int32 skin_height;
            public Int32 frame_size;
            public Int32 skin_count;
            public Int32 vertex_count;
            public Int32 texture_coordinate_count;
            public Int32 triangle_count;
            public Int32 gl_command_count;
            public Int32 frame_count;
            public Int32 skin_offset;
            public Int32 texture_coordinate_offset;
            public Int32 triangle_offset;
            public Int32 frame_offset;
            public Int32 gl_command_offset;
            public Int32 end_offset;
        }

        private class MD2Vertex
        {
            public Byte x;
            public Byte y;
            public Byte z;
            public Byte normal_index;
        }

        private class MD2Frame
        {
            public Vector3 scale = new Vector3();
            public Vector3 translate = new Vector3();
            public String name;
            public List<MD2Vertex> vertices = new List<MD2Vertex>();
        }

        private class MD2Triangle
        {
            public UInt16[] vertex_indices = new UInt16[3];
            public UInt16[] texture_indices = new UInt16[3];
        }

        private class MD2Model
        {
            #region "normals"
            private Vector3[] normals = 
            {
                new Vector3(-0.525731f, 0.000000f, 0.850651f), 
                new Vector3(-0.442863f, 0.238856f, 0.864188f), 
                new Vector3(-0.295242f, 0.000000f, 0.955423f), 
                new Vector3(-0.309017f, 0.500000f, 0.809017f), 
                new Vector3(-0.162460f, 0.262866f, 0.951056f), 
                new Vector3(0.000000f, 0.000000f, 1.000000f), 
                new Vector3(0.000000f, 0.850651f, 0.525731f), 
                new Vector3(-0.147621f, 0.716567f, 0.681718f), 
                new Vector3(0.147621f, 0.716567f, 0.681718f), 
                new Vector3(0.000000f, 0.525731f, 0.850651f), 
                new Vector3(0.309017f, 0.500000f, 0.809017f), 
                new Vector3(0.525731f, 0.000000f, 0.850651f), 
                new Vector3(0.295242f, 0.000000f, 0.955423f), 
                new Vector3(0.442863f, 0.238856f, 0.864188f), 
                new Vector3(0.162460f, 0.262866f, 0.951056f), 
                new Vector3(-0.681718f, 0.147621f, 0.716567f), 
                new Vector3(-0.809017f, 0.309017f, 0.500000f), 
                new Vector3(-0.587785f, 0.425325f, 0.688191f), 
                new Vector3(-0.850651f, 0.525731f, 0.000000f), 
                new Vector3(-0.864188f, 0.442863f, 0.238856f), 
                new Vector3(-0.716567f, 0.681718f, 0.147621f), 
                new Vector3(-0.688191f, 0.587785f, 0.425325f), 
                new Vector3(-0.500000f, 0.809017f, 0.309017f), 
                new Vector3(-0.238856f, 0.864188f, 0.442863f), 
                new Vector3(-0.425325f, 0.688191f, 0.587785f), 
                new Vector3(-0.716567f, 0.681718f, -0.147621f), 
                new Vector3(-0.500000f, 0.809017f, -0.309017f), 
                new Vector3(-0.525731f, 0.850651f, 0.000000f), 
                new Vector3(0.000000f, 0.850651f, -0.525731f), 
                new Vector3(-0.238856f, 0.864188f, -0.442863f), 
                new Vector3(0.000000f, 0.955423f, -0.295242f), 
                new Vector3(-0.262866f, 0.951056f, -0.162460f), 
                new Vector3(0.000000f, 1.000000f, 0.000000f), 
                new Vector3(0.000000f, 0.955423f, 0.295242f), 
                new Vector3(-0.262866f, 0.951056f, 0.162460f), 
                new Vector3(0.238856f, 0.864188f, 0.442863f), 
                new Vector3(0.262866f, 0.951056f, 0.162460f), 
                new Vector3(0.500000f, 0.809017f, 0.309017f), 
                new Vector3(0.238856f, 0.864188f, -0.442863f), 
                new Vector3(0.262866f, 0.951056f, -0.162460f), 
                new Vector3(0.500000f, 0.809017f, -0.309017f), 
                new Vector3(0.850651f, 0.525731f, 0.000000f), 
                new Vector3(0.716567f, 0.681718f, 0.147621f), 
                new Vector3(0.716567f, 0.681718f, -0.147621f), 
                new Vector3(0.525731f, 0.850651f, 0.000000f), 
                new Vector3(0.425325f, 0.688191f, 0.587785f), 
                new Vector3(0.864188f, 0.442863f, 0.238856f), 
                new Vector3(0.688191f, 0.587785f, 0.425325f), 
                new Vector3(0.809017f, 0.309017f, 0.500000f), 
                new Vector3(0.681718f, 0.147621f, 0.716567f), 
                new Vector3(0.587785f, 0.425325f, 0.688191f), 
                new Vector3(0.955423f, 0.295242f, 0.000000f), 
                new Vector3( 1.000000f, 0.000000f, 0.000000f), 
                new Vector3(0.951056f, 0.162460f, 0.262866f), 
                new Vector3(0.850651f, -0.525731f, 0.000000f), 
                new Vector3(0.955423f, -0.295242f, 0.000000f), 
                new Vector3(0.864188f, -0.442863f, 0.238856f), 
                new Vector3(0.951056f, -0.162460f, 0.262866f), 
                new Vector3(0.809017f, -0.309017f, 0.500000f), 
                new Vector3(0.681718f, -0.147621f, 0.716567f), 
                new Vector3(0.850651f, 0.000000f, 0.525731f), 
                new Vector3(0.864188f, 0.442863f, -0.238856f), 
                new Vector3(0.809017f, 0.309017f, -0.500000f), 
                new Vector3(0.951056f, 0.162460f, -0.262866f), 
                new Vector3(0.525731f, 0.000000f, -0.850651f), 
                new Vector3(0.681718f, 0.147621f, -0.716567f), 
                new Vector3(0.681718f, -0.147621f, -0.716567f), 
                new Vector3(0.850651f, 0.000000f, -0.525731f), 
                new Vector3(0.809017f, -0.309017f, -0.500000f), 
                new Vector3(0.864188f, -0.442863f, -0.238856f), 
                new Vector3(0.951056f, -0.162460f, -0.262866f), 
                new Vector3(0.147621f, 0.716567f, -0.681718f), 
                new Vector3(0.309017f, 0.500000f, -0.809017f), 
                new Vector3(0.425325f, 0.688191f, -0.587785f), 
                new Vector3(0.442863f, 0.238856f, -0.864188f), 
                new Vector3(0.587785f, 0.425325f, -0.688191f), 
                new Vector3(0.688191f, 0.587785f, -0.425325f), 
                new Vector3(-0.147621f, 0.716567f, -0.681718f), 
                new Vector3(-0.309017f, 0.500000f, -0.809017f), 
                new Vector3(0.000000f, 0.525731f, -0.850651f), 
                new Vector3(-0.525731f, 0.000000f, -0.850651f), 
                new Vector3(-0.442863f, 0.238856f, -0.864188f), 
                new Vector3(-0.295242f, 0.000000f, -0.955423f), 
                new Vector3(-0.162460f, 0.262866f, -0.951056f), 
                new Vector3(0.000000f, 0.000000f, -1.000000f), 
                new Vector3(0.295242f, 0.000000f, -0.955423f), 
                new Vector3(0.162460f, 0.262866f, -0.951056f), 
                new Vector3(-0.442863f, -0.238856f, -0.864188f), 
                new Vector3(-0.309017f, -0.500000f, -0.809017f), 
                new Vector3(-0.162460f, -0.262866f, -0.951056f), 
                new Vector3(0.000000f, -0.850651f, -0.525731f), 
                new Vector3(-0.147621f, -0.716567f, -0.681718f), 
                new Vector3(0.147621f, -0.716567f, -0.681718f), 
                new Vector3(0.000000f, -0.525731f, -0.850651f), 
                new Vector3(0.309017f, -0.500000f, -0.809017f), 
                new Vector3(0.442863f, -0.238856f, -0.864188f), 
                new Vector3(0.162460f, -0.262866f, -0.951056f), 
                new Vector3(0.238856f, -0.864188f, -0.442863f), 
                new Vector3(0.500000f, -0.809017f, -0.309017f), 
                new Vector3(0.425325f, -0.688191f, -0.587785f), 
                new Vector3(0.716567f, -0.681718f, -0.147621f), 
                new Vector3(0.688191f, -0.587785f, -0.425325f), 
                new Vector3(0.587785f, -0.425325f, -0.688191f), 
                new Vector3(0.000000f, -0.955423f, -0.295242f), 
                new Vector3(0.000000f, -1.000000f, 0.000000f), 
                new Vector3(0.262866f, -0.951056f, -0.162460f), 
                new Vector3(0.000000f, -0.850651f, 0.525731f), 
                new Vector3(0.000000f, -0.955423f, 0.295242f), 
                new Vector3(0.238856f, -0.864188f, 0.442863f), 
                new Vector3(0.262866f, -0.951056f, 0.162460f), 
                new Vector3(0.500000f, -0.809017f, 0.309017f), 
                new Vector3(0.716567f, -0.681718f, 0.147621f), 
                new Vector3(0.525731f, -0.850651f, 0.000000f), 
                new Vector3(-0.238856f, -0.864188f, -0.442863f), 
                new Vector3(-0.500000f, -0.809017f, -0.309017f), 
                new Vector3(-0.262866f, -0.951056f, -0.162460f), 
                new Vector3(-0.850651f, -0.525731f, 0.000000f), 
                new Vector3(-0.716567f, -0.681718f, -0.147621f), 
                new Vector3(-0.716567f, -0.681718f, 0.147621f), 
                new Vector3(-0.525731f, -0.850651f, 0.000000f), 
                new Vector3(-0.500000f, -0.809017f, 0.309017f), 
                new Vector3(-0.238856f, -0.864188f, 0.442863f), 
                new Vector3(-0.262866f, -0.951056f, 0.162460f), 
                new Vector3(-0.864188f, -0.442863f, 0.238856f), 
                new Vector3(-0.809017f, -0.309017f, 0.500000f), 
                new Vector3(-0.688191f, -0.587785f, 0.425325f), 
                new Vector3(-0.681718f, -0.147621f, 0.716567f), 
                new Vector3(-0.442863f, -0.238856f, 0.864188f), 
                new Vector3(-0.587785f, -0.425325f, 0.688191f), 
                new Vector3(-0.309017f, -0.500000f, 0.809017f), 
                new Vector3(-0.147621f, -0.716567f, 0.681718f), 
                new Vector3(-0.425325f, -0.688191f, 0.587785f), 
                new Vector3(-0.162460f, -0.262866f, 0.951056f), 
                new Vector3(0.442863f, -0.238856f, 0.864188f), 
                new Vector3(0.162460f, -0.262866f, 0.951056f), 
                new Vector3(0.309017f, -0.500000f, 0.809017f), 
                new Vector3(0.147621f, -0.716567f, 0.681718f), 
                new Vector3(0.000000f, -0.525731f, 0.850651f), 
                new Vector3(0.425325f, -0.688191f, 0.587785f), 
                new Vector3(0.587785f, -0.425325f, 0.688191f), 
                new Vector3(0.688191f, -0.587785f, 0.425325f), 
                new Vector3(-0.955423f, 0.295242f, 0.000000f), 
                new Vector3(-0.951056f, 0.162460f, 0.262866f), 
                new Vector3(-1.000000f, 0.000000f, 0.000000f), 
                new Vector3(-0.850651f, 0.000000f, 0.525731f), 
                new Vector3(-0.955423f, -0.295242f, 0.000000f), 
                new Vector3(-0.951056f, -0.162460f, 0.262866f), 
                new Vector3(-0.864188f, 0.442863f, -0.238856f), 
                new Vector3(-0.951056f, 0.162460f, -0.262866f), 
                new Vector3(-0.809017f, 0.309017f, -0.500000f), 
                new Vector3(-0.864188f, -0.442863f, -0.238856f), 
                new Vector3(-0.951056f, -0.162460f, -0.262866f), 
                new Vector3(-0.809017f, -0.309017f, -0.500000f), 
                new Vector3(-0.681718f, 0.147621f, -0.716567f), 
                new Vector3(-0.681718f, -0.147621f, -0.716567f), 
                new Vector3(-0.850651f, 0.000000f, -0.525731f), 
                new Vector3(-0.688191f, 0.587785f, -0.425325f), 
                new Vector3(-0.587785f, 0.425325f, -0.688191f), 
                new Vector3(-0.425325f, 0.688191f, -0.587785f), 
                new Vector3(-0.425325f, -0.688191f, -0.587785f), 
                new Vector3(-0.587785f, -0.425325f, -0.688191f), 
                new Vector3(-0.688191f, -0.587785f, -0.425325f)
            };
            #endregion

            public MD2Header Header = new MD2Header();
            public String TextureName = "";

            public List<MD2Triangle> Triangles = new List<MD2Triangle>();
            public List<MD2Frame> Frames = new List<MD2Frame>();

            public List<Vector2> TextureCoordinates = new List<Vector2>();

            public Boolean IsVaild()
            {
                return (Header.magic_number == 844121161) && (Header.version == 8);
            }

            public Int32[] BuildIndices()
            {
                List<Int32> indices = new List<int>();
                foreach (MD2Triangle triangle in Triangles)
                {
                    indices.Add(triangle.vertex_indices[0]);
                    indices.Add(triangle.vertex_indices[1]);
                    indices.Add(triangle.vertex_indices[2]);                                        
                }
                return indices.ToArray();
            }

            public Vertex[] GetVertices(int frame_number)
            {
                List<Vertex> vertices = new List<Vertex>();
                MD2Frame frame = Frames[frame_number];

                for (int i = 0; i < Header.vertex_count; ++i)
                {
                    Vertex vertex = new Vertex();
                    MD2Vertex md2_vertex = frame.vertices[i];
                    vertex.Position.Z = -(frame.scale.X * md2_vertex.x + frame.translate.X);
                    vertex.Position.X = -(frame.scale.Y * md2_vertex.y + frame.translate.Y);
                    vertex.Position.Y = (frame.scale.Z * md2_vertex.z + frame.translate.Z);
                    vertex.Normal.Z = -normals[md2_vertex.normal_index].X;
                    vertex.Normal.X = -normals[md2_vertex.normal_index].Y;
                    vertex.Normal.Y = normals[md2_vertex.normal_index].Z;
                    vertices.Add(vertex);
                }

                Vertex[] vertex_array = vertices.ToArray();

                foreach (MD2Triangle triangle in Triangles)
                {
                    vertex_array[triangle.vertex_indices[0]].TextureCoordinate0 = TextureCoordinates[triangle.texture_indices[0]];
                    vertex_array[triangle.vertex_indices[1]].TextureCoordinate0 = TextureCoordinates[triangle.texture_indices[1]];
                    vertex_array[triangle.vertex_indices[2]].TextureCoordinate0 = TextureCoordinates[triangle.texture_indices[2]];                                        
                }

                return vertex_array;
            }
        }

        public List<String> GetSupportFormats()
        {
            List<String> support_formats = new List<String>();
            support_formats.Add(".MD2");
            return support_formats;
        }

        public ResourceType GetResourceType()
        {
            return ResourceType.ANIMATION_MESH;
        }

        private String GetAnimationName(String frame_name)
        { 
            String numbers = "0123456789";
            int index = frame_name.IndexOfAny(numbers.ToCharArray());
            if (index == -1)
                return "";

            return frame_name.Substring(0, index);
        }

        public Object LoadResource(String path)
        {
            try
            {
                path = System.IO.Path.Combine("Content", path);

                System.IO.FileStream file = System.IO.File.OpenRead(path);
                System.IO.BinaryReader binary_reader = new System.IO.BinaryReader(file);

                MD2Model model = new MD2Model();

                // MD2Header model.Header = Helper.ReadStruct<MD2Header>(binary_reader);

                model.Header.magic_number = binary_reader.ReadInt32();
                model.Header.version = binary_reader.ReadInt32();
                model.Header.skin_width = binary_reader.ReadInt32();
                model.Header.skin_height = binary_reader.ReadInt32();
                model.Header.frame_size = binary_reader.ReadInt32();
                model.Header.skin_count = binary_reader.ReadInt32();
                model.Header.vertex_count = binary_reader.ReadInt32();
                model.Header.texture_coordinate_count = binary_reader.ReadInt32();
                model.Header.triangle_count = binary_reader.ReadInt32();
                model.Header.gl_command_count = binary_reader.ReadInt32();
                model.Header.frame_count = binary_reader.ReadInt32();
                model.Header.skin_offset = binary_reader.ReadInt32();
                model.Header.texture_coordinate_offset = binary_reader.ReadInt32();
                model.Header.triangle_offset = binary_reader.ReadInt32();
                model.Header.frame_offset = binary_reader.ReadInt32();
                model.Header.gl_command_offset = binary_reader.ReadInt32();
                model.Header.end_offset = binary_reader.ReadInt32();

                // is md2 file?
                if (!model.IsVaild())
                {
                    file.Close();
                    return null;
                }

                // have skin?
                if (model.Header.skin_count > 0)
                {
                    // only read first skin
                    file.Seek(model.Header.skin_offset, System.IO.SeekOrigin.Begin);
                    model.TextureName = System.Text.Encoding.ASCII.GetString(binary_reader.ReadBytes(64)).Trim("\0".ToCharArray());
                }
                else
                { 
                    // set default skin
                    model.TextureName = System.IO.Path.GetFileName(path);
                }

                file.Seek(model.Header.texture_coordinate_offset, System.IO.SeekOrigin.Begin);
                for (int i = 0; i < model.Header.texture_coordinate_count; ++i)
                {
                    Vector2 texture_coordinate = new Vector2();
                    texture_coordinate.X = (float)binary_reader.ReadInt16() / (float)model.Header.skin_width;
                    texture_coordinate.Y = (float)binary_reader.ReadInt16() / (float)model.Header.skin_height;
                    model.TextureCoordinates.Add(texture_coordinate);
                }

                file.Seek(model.Header.triangle_offset, System.IO.SeekOrigin.Begin);
                for (int i = 0; i < model.Header.triangle_count; ++i)
                {
                    MD2Triangle triangle = new MD2Triangle();
                    for (int j = 0; j < 3; ++j)
                        triangle.vertex_indices[j] = binary_reader.ReadUInt16();
                    for (int j = 0; j < 3; ++j)
                        triangle.texture_indices[j] = binary_reader.ReadUInt16();
                    model.Triangles.Add(triangle);
                }

                file.Seek(model.Header.frame_offset, System.IO.SeekOrigin.Begin);
                for (int i = 0; i < model.Header.frame_count; ++i)
                {
                    MD2Frame frame = new MD2Frame();
                    frame.scale.X = binary_reader.ReadSingle();
                    frame.scale.Y = binary_reader.ReadSingle();
                    frame.scale.Z = binary_reader.ReadSingle();
                    frame.translate.X = binary_reader.ReadSingle();
                    frame.translate.Y = binary_reader.ReadSingle();
                    frame.translate.Z = binary_reader.ReadSingle();
                    frame.name = System.Text.Encoding.ASCII.GetString(binary_reader.ReadBytes(16)).Trim("\0".ToCharArray());

                    for (int j = 0; j < model.Header.vertex_count; ++j)
                    {
                        MD2Vertex vertex = new MD2Vertex();
                        vertex.x = binary_reader.ReadByte();
                        vertex.y = binary_reader.ReadByte();
                        vertex.z = binary_reader.ReadByte();
                        vertex.normal_index = binary_reader.ReadByte();
                        frame.vertices.Add(vertex);
                    }

                    model.Frames.Add(frame);
                }

                file.Close();

                AnimationMesh mesh = new AnimationMesh();
                StaticMeshPart mesh_part = new StaticMeshPart();
                MRTBasicMaterial material = new MRTBasicMaterial();
                material.DiffuseTexture.Create(Helper.GetFullPathWithoutExtension(model.TextureName) + ".IMAGE2D", false);
                mesh_part.Material = material;

                MeshBuffer mesh_buffer = new MeshBuffer();

                List<Vertex> vertices = new List<Vertex>();
                List<Int32> indices = new List<int>();

                VertexAnimation current_animation = null;
                String last_animation_name = "";
                int first_frame_index = -1;
                float current_time = 0.0f;
                
                for (int i = 0; i < model.Frames.Count; ++i)
                {                    
                    String animation_name = GetAnimationName(model.Frames[i].name);

                    if (animation_name != last_animation_name || current_animation == null)
                    {
                        
                        if (current_animation != null && first_frame_index != -1)
                        {
                            VertexAnimationFrame last_frame = new VertexAnimationFrame();
                            last_frame.MeshParts.Add("MD2", model.GetVertices(first_frame_index));
                            last_frame.Time = current_time;
                            current_animation.Frames.Add(last_frame);
                        }
                        
                        first_frame_index = i;
                        current_time = 0.0f;
                        current_animation = new VertexAnimation();
                        last_animation_name = animation_name;
                        mesh.Animations.Add(animation_name, current_animation);
                    }

                    VertexAnimationFrame frame = new VertexAnimationFrame();
                    frame.Time = current_time;
                    frame.MeshParts.Add("MD2", model.GetVertices(i));
                    current_animation.Frames.Add(frame);
                    current_time += 0.1f;
                }

                mesh_buffer.SetVertices(model.GetVertices(0));
                mesh_buffer.SetIndices(model.BuildIndices());

                mesh_part.SetMeshBuffer(mesh_buffer);

                mesh.AddMeshPart("MD2", mesh_part);

                return mesh; ;
            }
            catch
            {
                return null;
            }
        }
    }
}
